// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: daniel

#include "coin2/exchange/lmax_digital/api_order/fix/fix_trade_client.h"

#include <utility>

#include "coin2/exchange/base/api_util/api_signature.h"
#include "coin2/exchange/base/api_util/auth_key.h"
#include "quickfix/Application.h"
#include "quickfix/Session.h"
#include "quickfix/SessionSettings.h"

namespace coin2::exchange::lmax_digital::api_order::fix {

FixTradeClient::FixTradeClient(std::string_view auth_key_file)
    : FixTradeClientBase<FixTradeClient>(auth_key_file) {
  SetSessionSetting(trade_client_config_);
  msg_builder_ = std::make_unique<FixMessageBuilder>();
}

void FixTradeClient::onCreate(const FIX::SessionID&) {
  // do nothing
}

void FixTradeClient::onLogon(const FIX::SessionID&) {
  IsLogon(true);

  coin::proto::ErrorCode error_code;
  error_code.set_error_code(0);
  error_code.set_error_message("login");
  error_code.set_category("LMAX_FIX_CLIENT");
  PostSystemMsg(error_code);
}

void FixTradeClient::onLogout(const FIX::SessionID& session_id) {
  IsLogon(false);

  coin::proto::ErrorCode error_code;
  error_code.set_error_code(0);
  error_code.set_error_message("logout");
  error_code.set_category("LMAX_FIX_CLIENT");
  PostSystemMsg(error_code);
}

void FixTradeClient::toAdmin(FIX::Message& message, const FIX::SessionID& session_id) {
  FIX::MsgType msg_type;
  message.getHeader().getFieldIfSet(msg_type);
  if (msg_type == "A") {
    FillLogonMessage(message);
  }
}

void FixTradeClient::toApp(FIX::Message&, const FIX::SessionID&) EXCEPT(FIX::DoNotSend) {
  // do nothing
}

void FixTradeClient::fromAdmin(const FIX::Message& message, const FIX::SessionID& session_id)
    EXCEPT(FIX::FieldNotFound, FIX::IncorrectDataFormat, FIX::IncorrectTagValue, FIX::RejectLogon) {
  // ProcessIncomeMsg(message, session_id);
}
void FixTradeClient::fromApp(const FIX::Message& message, const FIX::SessionID& session_id) EXCEPT(
    FIX::FieldNotFound,
    FIX::IncorrectDataFormat,
    FIX::IncorrectTagValue,
    FIX::UnsupportedMessageType) {
  ProcessIncomeMsg(message, session_id);
}

void FixTradeClient::FillLogonMessage(FIX::Message& message) {
  try {
    // 1. get setting
    FIX::SessionID session_id = message.getSessionID();
    const FIX::Dictionary* settings = getInitiator().getSessionSettings(session_id);
    if (settings == nullptr) {
      return;
    }
    std::string password;
    if (settings->has(PASSWORD)) {
      password = settings->getString(PASSWORD);
    }
    if (password.empty()) {
      LOG(ERROR) << "password is not found";
    }

    message.setField(FIX::RawDataLength(password.length()));
    message.setField(FIX::RawData(password));
  } catch (FIX::Exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  } catch (std::exception& ex) {
    LOG(ERROR) << "exception on fill logon message: " << ex.what() << "\n";
  }
}

void FixTradeClient::Start() { FixTradeClientBase<FixTradeClient>::StartBase(); }

void FixTradeClient::Stop() { FixTradeClientBase<FixTradeClient>::StopBase(); }

bool FixTradeClient::IsAvailable() const {
  return FixTradeClientBase<FixTradeClient>::IsAvailable();
}

void FixTradeClient::SetExecutor(Executor* executor) {
  FixTradeClientBase<FixTradeClient>::SetExecutor(executor);
}

void FixTradeClient::SetProcessor(Processor* processor) {
  FixTradeClientBase<FixTradeClient>::SetProcessor(processor);
}

void FixTradeClient::AddSubscriber(SubscriberCB cb) {
  FixTradeClientBase<FixTradeClient>::AddSubscriberV2(cb);
}

void FixTradeClient::PlaceOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GeneratePlaceOrder(ord_exec, GetProductHolder());
  if (!message) {
    LOG(ERROR) << "failed to generate place order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::CancelOrder(const OrderExecution& ord_exec) {
  CHECK(msg_builder_);
  auto message = msg_builder_->GenerateCancelOrder(ord_exec);
  if (!message) {
    LOG(ERROR) << "failed to generate cancel order: " << ord_exec.proc_order_id;
    return;
  }
  this->Send(std::move(message));
}

void FixTradeClient::QueryOrder(const OrderExecution& ord_exec) {
  // unsupported
  CHECK(false) << "query order is unsupported";
}

void FixTradeClient::AssignSubSystemAuthKey(FIX::SessionSettings* session_setting) {
  auto auth_key = AuthKey::FromFile(GetAuthKey());
  ValidateSubSystemAuthKey(auth_key);
  std::string sender_comp_id = auth_key.access_key();
  FIX::Dictionary dict;
  dict.setString(SECRET_KEY, auth_key.secret_key());
  if (auto passphrase = auth_key.GetValue(PASSPHRASE); passphrase) {
    dict.setString(PASSWORD, *passphrase);
  }
  if (auto source_host = auth_key.GetValue("ip"); source_host) {
    dict.setString(SOURCE_HOST, *source_host);
  }
  FIX::SessionID session_id{begin_string_, sender_comp_id, target_comp_id_};
  session_setting->set(session_id, dict);
}

void FixTradeClient::ValidateSubSystemAuthKey(const AuthKey& auth_key) const {
  // TODO(daniel) - validate
}

}  // namespace coin2::exchange::lmax_digital::api_order::fix
